package hikari.persistence.propertieseditor;

import com.gooddies.ballontips.ToolTipHandler;
import com.gooddies.events.BooleanEvent;
import com.gooddies.graphics.InteractiveIcon;
import com.gooddies.graphics.LinkIconLabel;
import com.gooddies.texteditors.validation.AbstractValidator;
import hikari.persistence.file.FileSection;
import hikari.persistence.propertieseditor.annotation.Property;
import hikari.persistence.propertieseditor.editors.HelpProvider;
import hikari.persistence.propertieseditor.editors.IEditorRegistry;
import hikari.persistence.propertieseditor.editors.IPropertyEditor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import resources.Icons;

/**
 * @author sad
 */
@SuppressWarnings("unchecked")
public class EditorPanel extends JPanel {

    protected static final String UNDEFINED = "undefined";
    protected Object propertyStorage;
    protected HelpProvider helpProvider = new HelpProvider();
    protected List<Chunk> chunks;

    public EditorPanel() {
        setLayout(new MigLayout());
    }

    public void setHelpProvider(HelpProvider helpProvider) {
        if (helpProvider != null) {
            this.helpProvider = helpProvider;
        }
    }

    public void fillPanelWithData(Object propStorage) {
        try {
            propertyStorage = propStorage;
            chunks = extractProperties(propStorage);
            for (final Chunk chunk : chunks) {
                JLabel label = new JLabel(chunk.caption);
                add(label);

                add(chunk.editor.getJComponent(), chunk.help != null ? "" : "wrap");
                if (chunk.help != null) {
                    final LinkIconLabel help = new LinkIconLabel(null, new InteractiveIcon((ImageIcon) Icons.HELP_ICON));
                    help.addClickEvent(new BooleanEvent() {
                        @Override
                        public boolean fireEvent(Object aThis) {
                            String helpText;
                            if (helpProvider == null) {
                                helpText = chunk.help;
                            } else {
                                helpText = helpProvider.getHelp(chunk.help, chunk.editor.getEditorValue());
                            }

                            ToolTipHandler tooltip = ToolTipHandler.informationHandler(help, helpText);
                            tooltip.setVisible(true);
                            return false;
                        }
                    });
                    add(help, "wrap");
                }
            }
        } catch (InstantiationException | IllegalAccessException ex) {
            Logger.getLogger(EditorPanel.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected List<Chunk> getChunks() {
        return chunks;
    }

    public void saveBackToObject() {
        for (Chunk chunk : getChunks()) {
            Object value = chunk.editor.getEditorValue();
            try {
                chunk.field.set(propertyStorage, value);
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                Logger.getLogger(EditorPanel.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    protected List<Chunk> extractProperties(Object propStorage) throws InstantiationException, IllegalAccessException {
        List<Chunk> tChunks = new ArrayList<>();
        List<Field> fields = extractFields(propStorage);
        for (Field field : fields) {
            Chunk chunk = processField(field);
            tChunks.add(chunk);
        }
        return tChunks;
    }

    protected Chunk processField(Field field) throws InstantiationException, IllegalAccessException {
        Chunk chunk = new Chunk();
        Property prop = field.getAnnotation(Property.class);
        String id=prop.id();
        chunk.id=id;
        String className = field.getType().getCanonicalName();
        Class<? extends IPropertyEditor> editorClass;
        if (prop.editor() == Property.DEFAULTEDITOR.class) {
            editorClass = IEditorRegistry.getEditor(className);
            if (editorClass == null) {
                throw new RuntimeException("Cannot instantiate field editor for class " + className);
            }
        } else {
            editorClass = prop.editor();
        }

        IPropertyEditor editor = editorClass.newInstance();
        Class validatorClass = prop.validator();
        if (validatorClass != Property.DEFAULTVALIDATOR.class) {
            AbstractValidator validator = (AbstractValidator) validatorClass.newInstance();
            editor.setValidator(validator);
        }
        Object value = field.get(propertyStorage);
        editor.setEditorValue(value);

        chunk.caption = prop.caption();
        chunk.help = prop.help().equals(UNDEFINED) ? null : prop.help();

        chunk.editor = editor;
        if (!prop.max().equals(UNDEFINED)) {
            editor.setMax(prop.max());
        }

        if (!prop.min().equals(UNDEFINED)) {
            editor.setMin(prop.min());
        }

        if (!prop.maxLength().equals(UNDEFINED)) {
            editor.setMaxLength(prop.maxLength());
        }

        chunk.field = field;
        return chunk;
    }

    protected List<Field> extractFields(Object propStorage) {
        List<Field> collectedFields = new ArrayList<>();
        Class clazz = propStorage.getClass();
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Property.class)) {
                    collectedFields.add(field);
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
                }
            }

            clazz = clazz.getSuperclass();
        }

        return collectedFields;
    }
    
    public void saveToFileSection(FileSection fileSection) throws IOException {
        for(Chunk chunk:getChunks()){
            chunk.editor.save(chunk.id, fileSection);
        }
    }
    
    public void readFromFileSection(FileSection fileSection) throws IOException {
        for(Chunk chunk:getChunks()){
            chunk.editor.read(chunk.id, fileSection);
        }
    }

    protected static class Chunk {

        private IPropertyEditor editor;
        private String id;
        private String caption;
        private String help;
        private AbstractValidator validator;
        private Field field;
    }
}
